/**
 * @file   Server.cpp
 * @author Djuro Drljaca (djurodrljaca@gmail.com)
 * @date   2013-03-14
 * @brief  DataBus Server.
 *
 * Copyright (C) 2013  Djuro Drljaca <djurodrljaca@gmail.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <DataBus/Server/Server.h>
#include <DataBus/Common/RegisterClientRequestPacket.h>
#include <DataBus/Common/RegisterClientResponsePacket.h>
#include <DataBus/Common/GetClientListResponsePacket.h>
#include <DataBus/Common/GetClientStatusRequestPacket.h>
#include <DataBus/Common/GetClientStatusResponsePacket.h>

DataBus::Server::Server(QObject *parent)
    : QObject(parent),
      m_tcpServer(parent),
      m_clients()
{
    // Notification of a new connected Client
    connect(&m_tcpServer, SIGNAL(newConnection()), this, SLOT(newClientConnected()));
}

DataBus::Server::~Server()
{
    // Stop and TCP Server
    stop();
}

bool DataBus::Server::start(quint16 port)
{
    // Check input data
    if (port == 0)
    {
        // Error, invalid port
        return false;
    }

    // Close the TCP server if it is already listening for incoming connections
    if (m_tcpServer.isListening())
    {
        m_tcpServer.close();
    }

    // Remove all clients from the list
    removeAllClients();

    // Start listening for new DataBus Clients
    if (m_tcpServer.listen(QHostAddress::Any, port) == false)
    {
        // Error, failed to start listening on the selected port
        return false;
    }

    // OK
    return true;
}

void DataBus::Server::stop()
{
    // Stop the server
    if (m_tcpServer.isListening())
    {
        m_tcpServer.close();
    }

    // Remove all clients
    removeAllClients();
}

bool DataBus::Server::isStarted() const
{
    // Return "started" state
    return m_tcpServer.isListening();
}

int DataBus::Server::getClientCount() const
{
    return m_clients.size();
}

const DataBus::Client * DataBus::Server::getClient(const int index) const
{
    // Check if index is valid
    if ((index < 0) ||
        (index >= m_clients.size()))
    {
        return 0;
    }

    // Return client
    return m_clients.at(index);
}

const DataBus::Client * DataBus::Server::getClientById(quint8 clientId) const
{
    // Find client
    for (int i = 0; i < m_clients.size(); i++)
    {
        const Client *client = m_clients.at(i);

        if (clientId == client->getClientId())
        {
            // Client found, return pointer to the found client
            return client;
        }
    }

    // Error, client was not found
    return 0;
}

int DataBus::Server::getClientIndex(const Client *client) const
{
    for (int i = 0; i < m_clients.size(); i++)
    {
        if (client == m_clients.at(i))
        {
            return i;
        }
    }

    return -1;
}

const QList<DataBus::Client *> * DataBus::Server::getClientList()
{
    return &m_clients;
}

void DataBus::Server::removeAllClients()
{
    while (m_clients.size() > 0)
    {
        // Remove the client from the list and close it
        Client *client = m_clients.takeFirst();

        if (client != 0)
        {
            client->close();
            client->deleteLater();
            client = 0;
        }
    }
}

void DataBus::Server::addClient(Client *client)
{
    m_clients.append(client);
}

bool DataBus::Server::removeClient(const int index)
{
    if ((index < 0) ||
        (index >= m_clients.size()))
    {
        return false;
    }

    Client *client = m_clients.takeAt(index);

    if (client != 0)
    {
        client->close();
        client->deleteLater();
        client = 0;
    }
    return true;
}

quint8 DataBus::Server::registerClient(const Client *client, const Packet &packet)
{
    // Check input parameters
    if ((client == 0) ||
        (packet.getSource() == 0) ||
        (packet.getDestination() != 0) ||
        (packet.getPacketType() != PacketType_RegisterClientRequest))
    {
        // Error, invalid input parameters
        return RETURN_STATUS_ERROR;
    }

    // Parse RegisterClient payload
    quint8 activityTimeout;
    quint8 pingResponseTimeout;
    quint8 pingRetryCount;

    if (RegisterClientRequestPacket::parse(packet,
                                           &activityTimeout,
                                           &pingResponseTimeout,
                                           &pingRetryCount) == false)
    {
        // Error, failed to parse RegisterClient payload
        return RETURN_STATUS_ERROR;
    }

    // Try to find an existing client with the same Client ID
    quint16 clientId = packet.getSource();
    Client *foundClient = 0;

    for (int i = 0; i < m_clients.size(); i++)
    {
        Client *item = m_clients.at(i);

        if (item == client)
        {
            // Save found client
            foundClient = item;

            // Skip client that is trying to register
            continue;
        }

        if (clientId == item->getClientId())
        {
            // Error, client with the same Client ID already exists
            return RETURN_STATUS_INVALID_CLIENT_ID;
        }
    }

    if (foundClient == 0)
    {
        // Error, failed to find client
        return RETURN_STATUS_ERROR;
    }

    // Register Client
    if (foundClient->registerClient(clientId,
                                    activityTimeout,
                                    pingResponseTimeout,
                                    pingRetryCount) == false)
    {
        // Error, failed to register client
        return RETURN_STATUS_ERROR;
    }

    // Success
    return RETURN_STATUS_SUCCESS;
}

void DataBus::Server::processPackets(Client *client)
{
    // Check client
    if (client == 0)
    {
        // Error, null pointer
        return;
    }

    // Find client in the list
    int index = m_clients.indexOf(client);

    if (index < 0)
    {
        // Error, client was not found
        return;
    }

    // Process all packets
    while (client->packetsAvailable())
    {
        // Process packet
        Packet packet = client->takePacket();

        switch (packet.getPacketType())
        {
            case PacketType_RegisterClientRequest:
            {
                // Try to register the client
                quint8 returnStatus = registerClient(client, packet);

                // Send Register Client Response
                Packet responsePacket;

                if (RegisterClientResponsePacket::create(packet.getSource(),
                                                         packet.getPacketId(),
                                                         returnStatus,
                                                         &responsePacket) == false)
                {
                    // Error, failed to create packet
                    break;
                }

                client->sendPacket(responsePacket);
                break;
            }

            case PacketType_GetClientListRequest:
            {
                // Create response
                Packet responsePacket;
                QList<quint8> clientIdList = getRegisteredClientIdList();

                if (GetClientListResponsePacket::create(packet.getSource(),
                                                        packet.getPacketId(),
                                                        clientIdList,
                                                        &responsePacket) == false)
                {
                    // Error, failed to create packet
                    break;
                }


                // Send Get Client List Response
                client->sendPacket(responsePacket);
                break;
            }

            case PacketType_GetClientStatusRequest:
            {
                // Parse packet
                quint8 clientId;

                if (DataBus::GetClientStatusRequestPacket::parse(packet,
                                                                 &clientId) == false)
                {
                    // Error, failed to parse packet
                    break;
                }

                // Create response
                ClientStatus clientStatus = getRegisteredClientStatus(clientId);

                Packet responsePacket;

                if (GetClientStatusResponsePacket::create(packet.getSource(),
                                                          packet.getPacketId(),
                                                          clientId,
                                                          clientStatus,
                                                          &responsePacket) == false)
                {
                    // Error, failed to create packet
                    break;
                }


                // Send Get Client Status Response
                client->sendPacket(responsePacket);
                break;
            }

            default:
            {
                // Forward packet to a client
                forwardPacket(packet);
                break;
            }
        }
    }
}

void DataBus::Server::forwardPacket(const Packet &packet)
{
    // Find destination client
    quint8 destination = packet.getDestination();

    if (destination == 0)
    {
        // Error, packet has to be sent to a client an not to the server
        // TODO: send error packet?
        return;
    }

    const Client *client = getClientById(destination);

    if (client == 0)
    {
        // Error, client was not found
        // TODO: send error packet?
        return;
    }

    // Forward packet to the client
    client->sendPacket(packet);
}

void DataBus::Server::newClientConnected()
{
    while (m_tcpServer.hasPendingConnections())
    {
        // Initialize new DataBus Client
        QTcpSocket *socket = m_tcpServer.nextPendingConnection();
        Client *client = new Client(this);

        if (client->initialize(socket) == false)
        {
            // Error, failed to initialize socsocketket
            if (socket != 0)
            {
                socket->close();
                socket->deleteLater();
                socket = 0;
            }

            continue;
        }

        connect(client, SIGNAL(clientDisconnected(Client*)), this, SLOT(clientDisconnected(Client*)));
        connect(client, SIGNAL(newPacketReceived(Client*)), this, SLOT(processReceivedPacket(Client*)));

        // Add client to list
        addClient(client);
    }
}

void DataBus::Server::clientDisconnected(Client *client)
{
    // Check client
    if (client == 0)
    {
        // Error, null pointer
        return;
    }

    // Find client in the list
    int index = m_clients.indexOf(client);

    if (index < 0)
    {
        // Error, client was not found
        return;
    }

    // Remove client from the list
    removeClient(index);
}

void DataBus::Server::processReceivedPacket(Client *client)
{
    // Check client
    if (client == 0)
    {
        // Error, null pointer
        return;
    }

    // Find client in the list
    int index = m_clients.indexOf(client);

    if (index < 0)
    {
        // Error, client was not found
        return;
    }

    // Process received packet from a client
    processPackets(client);
}

QList<quint8> DataBus::Server::getRegisteredClientIdList() const
{
    // Create registered client ID list
    QList<quint8> list;

    for (int i = 0; i < m_clients.size(); i++)
    {
        const Client * client = m_clients.at(i);

        if (client == 0)
        {
            continue;
        }

        // Save only registered clients
        quint8 clientId = client->getClientId();

        if (clientId != 0)
        {
            list.append(clientId);
        }
    }

    return list;
}

DataBus::ClientStatus DataBus::Server::getRegisteredClientStatus(const quint8 clientId) const
{
    // Get client
    const Client *client = getClientById(clientId);

    if (client == 0)
    {
        // Error, client was not found
        return ClientStatus_ErrorUnknownClient;
    }

    // Return client's status
    if (client->isActive())
    {
        // Client is active
        return ClientStatus_Active;
    }
    else
    {
        // Client is inactive
        return ClientStatus_Inactive;
    }
}
